Python 手记(二):装饰器

一、装饰器decorator的作用与组成:**

作用:在不修改被修饰函数主体及调用方式的前提下,为被修饰函数提供新的功能
组成:内嵌函数+高阶函数+闭包=》装饰器

内嵌(部)函数:

定义:在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>>
>>> num=1
>>> def level1():
... num=2
... print(num,'level1')
... def level2():
... num=3
... print(num,'level2')
... def level3():
... num=4
... print(num,'level3')
... level3()
... level2()
...
>>> level1()
2 level1
3 level2
4 level3
>>>

注释掉level3中的num赋值,则num继承上一级level2作用域中的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> num=1
>>> def level1():
... num=2
... print(num,'level1')
... def level2():
... num=3
... print(num,'level2')
... def level3():
... #num=4
... print(num,'level3')
... level3()
... level2()
...
>>> level1()
2 level1
3 level2
3 level3

注释掉level2、level3中的num赋值,则level2、level3中num均继承level1作用域中的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> num=1
>>> def level1():
... num=2
... print(num,'level1')
... def level2():
... #num=3
... print(num,'level2')
... def level3():
... #num=4
... print(num,'level3')
... level3()
... level2()
...
>>> level1()
2 level1
2 level2
2 level3
>>>

总结:变量在函数中的作用域只能正向继承,但层级越近优先级越高,本地优先级最高,全局优先级最低。

高阶函数:

满足下列条件之一的函数,即可被称之为高阶函数:
1.某一函数当做参数传入另一个函数中
2.函数的返回值包含n个函数,n>0

例如:

1
2
3
4
5
6
7
8
9
10
11
12
>>> def foo(func):   ####stage2
... res=func() #####stage3
... return res ####stage4
...
>>> def calc():
... print([i for i in range(3)])
... return 'run end'
...
>>>
>>> foo(calc) ####stage1
[0, 1, 2]
'run end'

执行过程:
stage1.将calc函数主体这个对象,calc作为实参传递给foo,替代形参func
stage2:运行foo函数
stage3:将calc()赋值给变量res,并开始执行calc()函数,
stage4:将clac()的函数返回值传递给res

好像并没有什么X用,绕了一圈不如直接调用calc(),那么接着往下

闭包

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行了引用,并且函数返回值也是内部函数本身,那么该内部函数就被认为是闭包(closure)。

1
2
3
4
5
6
7
8
def outside _func(para1):
def inside_func(para2):
return para1*para2
return inside_func # 将inside_func作为outside_func的返回值

a = outside_func(12) # 此时 para1=12,执行outside_func(12)
print(a) #此时的inside_func并未赋予参数被执行,因此上方执行的结果只能是一段inside_func的内存指针
print(a(21)) # para2=21,等价于print(outside_func(12)(21))

此时的inside_func就叫做闭包,闭包的作用:
1.隐藏内部函数,只暴露外部函数
2.如果内部函数使用了外部函数的变量,则此外部变量不会随着外部函数一起销毁,而会一直保存在内存中

二、内嵌函数+高阶函数+闭包=》装饰器

假设要给函数增加一个记录运行时间的功能,要求不改变函数主体和调用方式:

**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> import time
>>> def timmer(func): #stage3
... def warpper():
... start_time=time.time() #stage4
... func() #stage5,link to the function test1()
... stop_time=time.time() #stage6
... print('the func run time is %s second' %(stop_time-start_time))
... return warpper #stage7
...
>>>
>>> def test1():
... time.sleep(1)
... print('in the test1')
...
>>> test1 = timmer(test1) #stage1
>>> test1() #stage2
in the test1
the func run time is 1.0000572204589844 second
>>>

过程解析:
stage1:将test1函数作为实参传递给timmer函数,运算得出的结果的内存指针再重新指向给test1
stage2:开始执行test1(读取test1内存指针并开始运算)
stage3:执行timmer(func),即timmer(test1)
stage4:执行warpper,记录当前时间赋值给start_time
stage5:执行func(),即test1()函数
stage6:test1()执行完成后记录当前时间,赋值给stop_time
stage7:warpper的执行结果return给timmer,执行全部完成。

ps:stage1中的test1=timmer(test1)看起来比够简洁,在python中有语法糖这么一个存在,@符号表示,test1=timmer(test)等价于在定义test1的上方@timmer

**

函数在执行过程中可能会带有参数,那么带参数的函数如何配合装饰器中使用呢?改良版:

**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> import time
>>> def timmer(func):
... def warpper(*args,**kwargs): #可接受任意类型参数
... start_time=time.time()
... func(*args,**kwargs)
... stop_time=time.time()
... print('the func run time is %s second' %(stop_time-start_time))
... return warpper
...
>>>@timmer #等价于test1=timmer(test1)
>>> def test1(n):
... time.sleep(n)
... print('in the test1')
... return 'run end'
...
>>> test1(2) #等价于timmer(test1)(2),test1作为timmer的参数,2作为warpper的参数
in the test1
the func run time is 2.0001142024993896 second
>>>

函数可能会手动定义返回值,例如上方定义了return ‘run end’,但是经过装饰器包装了之后现在并没有返回值,如何保留函数原本的返回值呢?进阶版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> import time
>>> def timmer(func):
... def warpper(*args,**kwargs):
... start_time=time.time()
... func(*args,**kwargs)
... stop_time=time.time()
... print('the func run time is %s second' %(stop_time-start_time))
... return func(*args,**kwargs)
... return warpper
...
>>> @timmer
... def test1(n):
... time.sleep(n)
... return 'run end'
...
>>> test1(1)
the func run time is 1.0000572204589844 second
'run end'
>>>

在闭包函数内最后一行添加一个return func()即可返回func()函数原本的return返回值

若此时要求实现较为复杂的逻辑,装饰器本身也要携带不同的参数以实现多元化的功能。例如在上方例子基础上,要求增加在不同的板块内计时的倍率不同的功能。高级版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
>>> import time
>>> def timmer_rate(rate_type):
... print('Current rate is %s rate' % rate_type )
... def timmer(func):
... def warpper(*args,**kwargs):
... start_time=time.time()
... func(*args,**kwargs)
... stop_time=time.time()
... run_time=stop_time-start_time
... if rate_type == 'normal':
... print('the func run time is %s second' % run_time)
... elif rate_type == 'double':
... print('the func run time is %s second' %(run_time*2))
... return func(*args,**kwargs)
... return warpper
... return timmer
...
>>>
>>> @timmer_rate('normal')
... def test1(n):
... time.sleep(n)
... return 'test1 run end'
...
Current rate is normal rate
>>> @timmer_rate('double')
... def test2(n):
... time.sleep(n)
... return 'test2 run end'
...
Current rate is double rate
>>> test1(4)
the func run time is 4.0002288818359375 second
'test1 run end'
>>> test2(2)
the func run time is 4.0002288818359375 second
'test2 run end'
>>>

相比上方版本,只是在原本两层的装饰器外再添加了一层,可以用来接收装饰器本身的参数,在装饰器内层加以判断,以实现差异化的功能,最终在test1内执行4s和test2内执行2s,计时结果时间接近一致。

赏一瓶快乐回宅水吧~
-------------本文结束感谢您的阅读-------------